package logic.nodes.damagable;

import java.util.Iterator;

import sound.AudioManager;

import main.listeners.ListenerContainer;
import networking.messages.syncMessages.NodeSyncMessage;
import networking.messages.syncMessages.ValueType;

import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingVolume;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.scene.Node;

import factories.EffectFactory;
import fileHandling.language.properties.NodeProps;
import gameStates.absGamesStates.AbsIngameState;
import gameStates.network.IDObject;

import logic.nodes.MovingController;
import logic.nodes.collision.CollidableNode;
import logic.nodes.collision.DummyType;
import logic.nodes.collision.ImpulseController;
import logic.nodes.damagable.shield.Shields;
import logic.nodes.lod.LODNode;
import logic.nodes.lod.blocks.LeafBlock;
import logic.nodes.nodeSettings.DamagableSettings;
import logic.nodes.nodeSettings.upgrades.UpgradeEvent;
import logic.nodes.nodeSettings.upgrades.UpgradeListener;
import logic.status.StatusListener;
import logic.weapons.WeaponFire;

public abstract class DamagableNode extends LODNode implements IDObject {
	
	private static final long serialVersionUID = 1L;
	
	protected DamagableSettings settings;
	protected DamagableController statusController;
	protected int objectID;
	
	protected Shields shields;
	protected BoundingSphere shieldBound;
	
	public DamagableNode(String path, String title, Node model, DamagableSettings settings, 
			AbsIngameState ingameState) {
		this(path, title, model, settings, DummyType.None, ingameState);
	}
	
	public DamagableNode(String path, String title, Node model, DamagableSettings settings, DummyType dummyType, 
			AbsIngameState ingameState) {
		super(path, title, model, dummyType, ingameState);
		this.settings = settings;
		
		addUpgradeListeners();
		
		initValueContainer();
	}
	
	@Override
	public boolean allBoundsDone() {
		return super.allBoundsDone() && shieldBound != null;
	}
	
	@Override
	public void detachModel() {
		super.detachModel();
		detachChild(shields);
	}
	
	public void setShieldBound(BoundingSphere sphere) {
		shieldBound = sphere;
		shieldBound.setCenter(getLocalTranslation());
		shields = new Shields(this);
	}
	
	public BoundingSphere getShieldBound() { return shieldBound; }
	
	@Override
	public BoundingVolume getBound() {
		if(getCurrentShield() > 0 && shieldBound != null) return shieldBound;
		return super.getBound();
	}
	
	public void shieldImpact(CollidableNode other) {
		if(getCurrentShield() == 0 || !isVisibleToPlayer() || shieldBound == null) return;
		
		Vector3f thisToOther = other.getLocalTranslation().subtract(getLocalTranslation());
		thisToOther.normalizeLocal();
		thisToOther.multLocal(shieldBound.getRadius());
		Vector3f loc = getLocalTranslation().add(thisToOther);
		float dist = ingameState.getCam().getLocation().distance(loc);
		worldToLocal(loc, loc);
		if(dist <= LeafBlock.particleDist) shields.collision(loc);
	}
	
	protected void initValueContainer() {
		valueManager.changeValue(ValueType.HP, getMaxHP());
		valueManager.changeValue(ValueType.Shield, getMaxShield());
	}
	
	@Override
	protected void applyDummyType() {
		super.applyDummyType();
		switch(dummyType) {
			case None: setStatusController(new DamagableController(this)); break;
			case Client: setStatusController(new DamagableController(this)); break;
		}
	}
	
	protected void addUpgradeListeners() {
		settings.addUpgradeListener(new UpgradeListener() {
			@Override
			public void upgraded(UpgradeEvent event) {
				String key = event.getKey();
				if(key.equals(NodeProps.MaxHP)) changeHP(getMaxHP());
				else if(key.equals(NodeProps.MaxShields)) changeShields(getMaxShield());
			}
		});
	}
	
	protected void setStatusController(DamagableController newController) {
		if(statusController != null) removeController(statusController);
		statusController = newController;
		addController(statusController);
	}
	
	public void addStatusListener(StatusListener l, float diff) { valueManager.addListener(l, diff); }
	
	public void addStatusListener(StatusListener l) { valueManager.addListener(l); }
	
	public void removeStatusListener(StatusListener l) { valueManager.removeListener(l); }
	
	protected void changeHP(int newHP) {
		if(getCurrentHP() == newHP) return;
		
		if(newHP <= 0) {
			newHP = 0;
			destroy();
		}
		
		valueManager.changeValue(ValueType.HP, newHP);
	}
	
	protected void changeShields(int newShields) {
		if(newShields == 0 && statusController != null) 
			statusController.resetShieldCoolDown();
		if(getCurrentShield() == newShields) return;
		
		valueManager.changeValue(ValueType.Shield, newShields);
	}
	
	public boolean isDestroyed() { return getCurrentHP() == 0; }
	
	public boolean isDamaged() { return getCurrentHP() != getMaxHP(); }
	
	public void restore() {
		changeHP(getMaxHP());
		changeShields(getMaxShield());
	}
	
	public void restore(int amount) {
		int currentHP = getCurrentHP();
		if(currentHP + amount > getMaxHP()) amount = getMaxHP() - currentHP;
		changeHP(currentHP + amount);
	}
	
	@SuppressWarnings("unused")
	public void receiveDamage(WeaponFire weaponFire, int damage) {
		assert(!isDestroyed());
		receiveDamage(damage);
	}
	
	public void receiveDamage(WeaponFire weaponFire) {
		receiveDamage(weaponFire, weaponFire.getDamage());
	}
	
	public void receiveDamage(int damage) {
		int currentHP = getCurrentHP();
		int currentShield = getCurrentShield();
		assert(!isDestroyed());
		
		if(currentShield >= damage) changeShields(currentShield - damage);
		else {
			int restDamage = damage - currentShield;
			changeShields(0);
			changeHP(currentHP - restDamage);
		}
	}
	
	public void destroy() {
		if(LeafBlock.isVisible(getLocalTranslation(), ingameState) && parent != null) explode();
		
		Iterator<MovingController> iter = movingControllers.iterator();
		while(iter.hasNext()) {
			MovingController m = iter.next();
			if(m instanceof ImpulseController) {
				removeController((ImpulseController)m);
				iter.remove();
			}
		}
		
		removeFromParent();
	}
		
	protected void explode() {
		EffectFactory.createNodeExplosion(this);
		
		AudioManager.get().playSound(getDestructionSound(), getLocalTranslation());
		
		DestroyController c = new DestroyController(this);
		if(c.getModel() != null) c.getModel().addController(c);
	}
	
	public String getDestructionSound() {
		return AudioManager.EXPLOSION_01;
	}
	
	public int getShieldRecovery() {
		return settings.getShieldRecovery();
	}
	
	public void increaseShield(int amount) {
		int currentShield = getCurrentShield();
		if(currentShield + amount > getMaxShield()) amount = getMaxShield() - currentShield;
		if(amount > 0) changeShields(currentShield + amount);
	}
	
	public int getCurrentHP() { return valueManager.getIntValue(ValueType.HP); }
	
	public int getCurrentShield() { return valueManager.getIntValue(ValueType.Shield); }
	
	public int getMaxHP() { return settings.getMaxHP(); }
	
	public int getMaxShield() { return settings.getMaxShield(); }
	
	public DamagableSettings getSettings() { return settings; }
	
	@Override
	public float getMass() { return FastMath.pow(getMaxHP(), 4f); }
	
	@Override
	public void setID(int objectID) { this.objectID = objectID; }
	
	@Override
	public int getID() { return objectID; }
	
	public boolean applySyncMessage(NodeSyncMessage shipSyncMessage) {
		int value = shipSyncMessage.getIntValue();
		
		switch(shipSyncMessage.getType()) {
			case HP : changeHP(value); return true;
			case Shield : changeShields(value); return true;
		}
		return false;
	}
	
	@Override
	public ListenerContainer getListenerContainer(Class<?> listenerClass) {
		if(listenerClass == StatusListener.class) return valueManager.getDefaultContainer().getListenerContainer();
		return super.getListenerContainer(listenerClass);
	}
	
	@Override
	public void updateGeometryQuality() {
		if(shields != null) shields.updateQuality();
		super.updateGeometryQuality();
	}
	
	@Override
	public void updateTextureQuality() {
		if(shields != null) shields.updateQuality();
		super.updateTextureQuality();
	}
}